#ifndef NODE_HPP
#define NODE_HPP

#include <iostream>
#include <sstream>
#include <vector>
#include <typeinfo>
#include "table_symbole.hpp"

class NStatement;
class NDeclaration;
class NArithmetique;
class NVariable;
class NArgument;
class NProgramme;

typedef std::vector<NStatement*> statementsListe;
typedef std::vector<NDeclaration*> declarationsListe;
typedef std::vector<NArithmetique*> variableListe;
typedef std::vector<NArithmetique*> arithmetiqueListe;
typedef std::vector<NArgument*> argumentsdeclarationListe;
typedef std::vector<NProgramme*> programmedeclarationListe;

//faire un NVariable

class STATIQUES{
public:
  static int nombre_variable;
  static int L;
  static map<string,int> fonctions;
  static string derniere_variable;
  static string dernier_L;
};

using namespace std;

class Node {
public:
  string nomNode;
  Node(string NomNode) : nomNode(NomNode) {}
  virtual ~Node() {}  
  virtual void afficher(){}
  virtual void afficherMips(stringstream* ss){}
};

class NExpression : public Node {
public:
  NExpression(string NomNode) : Node(NomNode){}
  virtual void afficher(){}
  virtual void afficherMips(){}
};

// ARITHMETIQUE

class NArithmetique : public NExpression{
public:
  NArithmetique(string NomNode) : NExpression(NomNode) {}
  virtual void afficher(){}
  virtual void afficherMips(stringstream* ss){}
  virtual TableSymboleVariable* getVariable(){
    return NULL;
  }
};

class NN : public NArithmetique{
public:
  int value;
  NN(int value) : NArithmetique("NN"),value(value) { }
    
  void afficher(){
    cout<<value;
  }
  virtual void afficherMips(stringstream* ss){
    (*ss)<<value;
  }
};

class ND : public NArithmetique{
public:
  float value;
  ND(float value) : NArithmetique("ND"), value(value) { }
    
  void afficher(){
    cout<<value;
  }

  void afficherMips(stringstream* ss){
    (*ss)<<value;
  }
};

class NX : public NArithmetique{
public:
  std::string value;
  NX(std::string value) : NArithmetique("NX"),value(value) { }

  void afficher(){
    cout<<"NXXXXXX "<<value<<endl;
  }

  void afficherMips(stringstream* ss){
    (*ss)<<value;
  }
};

class NVariable : public NArithmetique{
public:
  std::string nom;
  int entry;
  NVariable(std::string nom) : NArithmetique("NVariable"),nom(nom) {
    entry=STATIQUES::nombre_variable++;
  }
    
  void afficher(){
    cout<<nom;
    //if(entry != -1)
    //    cout<<"entry("<<entry<<")";
  }

  void afficherMips(stringstream* ss){
    (*ss)<<"$"<<entry;
  }
};

class NArray : public NArithmetique{
public:
  std::string nom;
  NArithmetique& taille;
  NArray(std::string nom,NArithmetique& taille) : NArithmetique("NArray"), nom(nom), taille(taille) { }
    
  void afficher(){
    cout<<nom;
    cout<<"[";
    taille.afficher();
    cout<<"]";
  }
};

class NUnop : public NArithmetique{
public:
  NArithmetique& variable;
  NUnop(NArithmetique& variable) : NArithmetique("NUnop"), variable(variable) {}
    
  void afficher(){
    cout<<"-";
    variable.afficher();
  }

  void afficherMips(stringstream* ss){
    (*ss)<<"mul "<<"$v2, ";
    variable.afficherMips(ss);
    (*ss)<<", -1";
    (*ss)<<endl;
  }
};

class NBinOp : public NArithmetique{
public:
  NArithmetique& variable1;
  int operateur;
  NArithmetique& variable2;
  NBinOp(NArithmetique& variable1, int operateur, NArithmetique& variable2) : NArithmetique("NBinOp"), variable1(variable1), operateur(operateur), variable2(variable2) {}
    
  void afficher(){
    variable1.afficher();
    switch(operateur){
    case 1:cout<<'+';break;
    case 2:cout<<'-';break;
    case 3:cout<<'*';break;
    case 4:cout<<'/';break;
    }
    variable2.afficher();
  }

  void afficherMips(stringstream* ss){
    switch(operateur){
    case 1:(*ss)<<"add ";break;
    case 2:(*ss)<<"sub ";break;
    case 3:(*ss)<<"mul ";break;
    case 4:(*ss)<<"div ";break;
    }
    (*ss)<<"$v2,";
    variable1.afficherMips(ss);
    (*ss)<<",";
    variable2.afficherMips(ss);
    (*ss)<<endl;
  }
};


// BOOLEENE

class NBoolean : public NExpression{
public:
  NBoolean(string NomNoeud) : NExpression(NomNoeud){}
  virtual void afficher(){}
  virtual void afficherMips(stringstream* ss){}
};

class NTrue : public NBoolean{
public:
  NTrue() : NBoolean("NTrue"){}
  void afficher(){
    cout<<"true";
  }
  void afficherMips(stringstream* ss){
    (*ss)<<"1";
  }
};

class NFalse : public NBoolean{
public:
  NFalse() : NBoolean("NFalse"){}
  void afficher(){
    cout<<"false";
  }
  void afficherMips(stringstream* ss){
    (*ss)<<"0";
  }
};

class NCmp : public NBoolean{
public:
  NArithmetique& variable1;
  int comparateur;
  NArithmetique& variable2;
  NCmp(NArithmetique& variable1, int comparateur, NArithmetique& variable2) : NBoolean("NCmp"),variable1(variable1), comparateur(comparateur), variable2(variable2) {}
    
  void afficher(){
    variable1.afficher();
    switch(comparateur){
    case 1: cout<<"<";break;
    case 2: cout<<">";break;
    case 3: cout<<"<=";break;
    case 4: cout<<">=";break;
    case 5: cout<<"==";break;
    case 6: cout<<"<>";break;
    }
    variable2.afficher();
  }
  void afficherMips(stringstream* ss){
    switch(comparateur){
    case 1: (*ss)<<"bgt ";break;
    case 2: (*ss)<<"blt ";break;
    case 3: (*ss)<<"ble ";break;
    case 4: (*ss)<<"bge ";break;
    case 5: (*ss)<<"beq ";break;
    case 6: (*ss)<<"bne ";break;
    }
    variable1.afficherMips(ss);
    (*ss)<<",";
    variable2.afficherMips(ss);
  }
};

class NotBoolean : public NBoolean{
public:
  NBoolean& booleen;
  NotBoolean(NBoolean& booleen) : NBoolean("NotBoolean"),booleen(booleen){}
    
  void afficher(){
    cout<<"!";
    booleen.afficher();
  }
  void afficherMips(stringstream* ss){
    (*ss)<<"not $v2,";
    booleen.afficherMips(ss);
  }
};

class NOr : public NBoolean{
public:
  NBoolean& booleen1;
  NBoolean& booleen2;
  NOr(NBoolean& booleen1,NBoolean& booleen2) : NBoolean("NOr"),booleen1(booleen1),booleen2(booleen2){}
    
  void afficher(){
    booleen1.afficher();
    cout<<"||";
    booleen2.afficher();
  }
  void afficherMips(stringstream* ss){
    (*ss)<<"or $v2,";
    booleen1.afficherMips(ss);
    (*ss)<<",";
    booleen2.afficherMips(ss);
    (*ss)<<endl;
  }
};

class NAnd : public NBoolean{
public:
  NBoolean& booleen1;
  NBoolean& booleen2;
  NAnd(NBoolean& booleen1,NBoolean& booleen2) : NBoolean("NAnd"),booleen1(booleen1),booleen2(booleen2){}
    
  void afficher(){
    booleen1.afficher();
    cout<<"&&";
    booleen2.afficher();
  }
  void afficherMips(stringstream* ss){
    (*ss)<<"and $v2,";
    booleen1.afficherMips(ss);
    (*ss)<<",";
    booleen2.afficherMips(ss);
    (*ss)<<endl;
  }
};



// INSTRUCTIONS

class NInstruction : public Node {
public:
  NInstruction(string NomNoeud) : Node(NomNoeud) {}
  virtual void afficher(){}
  virtual void afficherMips(stringstream* ss){}
};

class NReturn : public NInstruction{
public:
  NReturn() : NInstruction("NReturn"){}
  void afficher(){
    cout<<"return;"<<endl;
  }
  void afficherMips(stringstream* ss){
    (*ss)<<"jump $ra"<<endl;
  }
};

class NReturnValeur : public NInstruction{
public:
  NArithmetique& valeur;
  NReturnValeur(NArithmetique& valeur): NInstruction("NReturnValeur"), valeur(valeur){}
    
  void afficher(){
    cout<<"return ";
    valeur.afficher();
    cout<<endl;
  }
  void afficherMips(stringstream* ss){
    (*ss)<<"move $v0,";
    valeur.afficherMips(ss);
    (*ss)<<"jump $ra"<<endl;
  }
};

class NAffectation : public NInstruction{
public:
  NArithmetique& variable;
  NArithmetique& valeur;
  NAffectation(NArithmetique& variable,NArithmetique& valeur): NInstruction("NAffectation"), variable(variable),valeur(valeur){}
    
  void afficher(){
    variable.afficher();
    cout<<":=";
    valeur.afficher();
  }
  void afficherMips(stringstream* ss){
    (*ss)<<"move ";
    variable.afficherMips(ss);
    (*ss)<<",";
    valeur.afficherMips(ss);
    (*ss)<<endl;
  }
};

class NNew : public NInstruction{ //a changer
public:
  NArithmetique& variable;
  std::string type;
  NArithmetique& valeur;
  NNew(NArithmetique& variable,std::string type, NArithmetique& valeur): NInstruction("NNew"),variable(variable),type(type),valeur(valeur){}
    
  void afficher(){
    variable.afficher();
    cout<<"=new("<<type<<")";
    cout<<"[";
    valeur.afficher();
    cout<<"]"<<endl;
  }
  void afficherMips(stringstream* ss){}
};

class NFree : public NInstruction{
public:
  NArithmetique& variable;
  NFree(NArithmetique& variable): NInstruction("NFree"), variable(variable){}
    
  void afficher(){
    cout<<"free(";
    variable.afficher();
    cout<<")"<<endl;
  }
  void afficherMips(stringstream* ss){}
};

class NAppelFonction : public NInstruction{
public:
  std::string nomFonction;
  arithmetiqueListe& arguments;
  NAppelFonction(std::string nomFonction,arithmetiqueListe& arguments) : NInstruction("NAppelFonction"), nomFonction(nomFonction), arguments(arguments) {}
    
  void afficher(){
    cout<<nomFonction<<"(";
    for(int i=0;i<arguments.size();++i){
      arguments[i]->afficher();
      cout<<",";
    }
    cout<<")";
  }
  void afficherMips(stringstream* ss){
    for(int i=0;i<arguments.size();++i){
      (*ss)<<"move $t"<<i<<" ";
      arguments[i]->afficherMips(ss);
      (*ss)<<endl;
    }
    int LL;
    if (STATIQUES::fonctions.find(nomFonction) == STATIQUES::fonctions.end() ){
      LL = STATIQUES::L++;
      STATIQUES::fonctions[nomFonction] = LL;
    }
    else{
      LL=STATIQUES::fonctions[nomFonction];
    }
    (*ss)<<"jal L"<<LL<<endl;
  }
};

class NEgalFonction : public NInstruction{
public:
  NArithmetique& variable;
  NInstruction& appel;
  NEgalFonction(NArithmetique& variable, NInstruction& appel) : NInstruction("NEgalFonction"), variable(variable), appel(appel){}
    
  void afficher(){
    variable.afficher();
    cout<<":=";
    appel.afficher();        
    cout<<endl;
  }
  void afficherMips(stringstream* ss){
    appel.afficherMips(ss);
    (*ss)<<"move ";
    variable.afficherMips(ss);
    (*ss)<<",$v0"<<endl;
  }
};


// STATEMENTS

class NStatement : public Node {
public:
  int LL;
  NStatement(string NomNoeud) : Node(NomNoeud){
    LL = STATIQUES::L++;
  }
  virtual void afficher(){}
  virtual void afficherMips(stringstream* ss){}
  void afficherMipsL(stringstream* ss){
    (*ss)<<LL;
  }
};

class NStatementInstruction : public NStatement {
public:
  NInstruction& instruction;
  NStatementInstruction(NInstruction& instruction) : NStatement("NStatementInstruction"),  instruction(instruction) {}
    
  void afficher(){
    instruction.afficher();
  }
  void afficherMips(stringstream* ss){
    instruction.afficherMips(ss);
  }
};

class NIf : public NStatement {
public:
  NBoolean& booleen;
  NStatement& statement1;
  NStatement& statement2;
  NIf(NBoolean& booleen, NStatement& statement1, NStatement& statement2) : NStatement("NIf"),booleen(booleen), statement1(statement1), statement2(statement2) {}
    
  void afficher(){
    cout<<endl<<"if";
    booleen.afficher();        
    cout<<" then "<<endl;
    statement1.afficher();
    cout<<endl<<"else"<<endl;
    statement2.afficher();
  }
  void afficherMips(stringstream* ss){
    (*ss)<<"#if"<<endl;
    (*ss)<<"L";
    afficherMipsL(ss);
    (*ss)<<":"<<endl;
    booleen.afficherMips(ss);
    (*ss)<<", ";
    statement1.afficherMipsL(ss);
    (*ss)<<endl;
    (*ss)<<"jump ";
    statement2.afficherMipsL(ss);
    (*ss)<<endl<<endl;

    (*ss)<<"#then"<<endl;
    statement1.afficherMips(ss);
    (*ss)<<endl<<endl;
    (*ss)<<"#else"<<endl;
    statement2.afficherMips(ss);
    (*ss)<<endl<<endl;
  }
};

class NWhile : public NStatement {
public:
  NBoolean& booleen;
  NStatement& statement;
  NWhile(NBoolean& booleen, NStatement& statement) : NStatement("NWhile"), booleen(booleen), statement(statement) {}
    
  void afficher(){
    cout<<"while";
    booleen.afficher();        
    cout<<" then "<<endl;
    statement.afficher();
  }

  void afficherMips(stringstream* ss){
    (*ss)<<"#while"<<endl;
    (*ss)<<"L";
    afficherMipsL(ss);
    (*ss)<<":"<<endl;
    booleen.afficherMips(ss);
    afficherMipsL(ss);

    (*ss)<<endl<<endl;
    statement.afficherMips(ss);
  }
};

class NDeclaration : public Node {
public:
  std::string type;
  variableListe& variables;
  NDeclaration(std::string type,variableListe& variables) : Node("NDeclaration"), type(type), variables(variables) {}
    
  void afficher(){
    cout<<type<<":";        
    for(int i=0;i<variables.size();++i){
      variables[i]->afficher();
      cout<<",";
    }
    cout<<endl;
  }
  void afficherMips(stringstream* ss){}
};

class NBlock : public NStatement {
public:
  declarationsListe& declarations;
  statementsListe& statements;
  NBlock(declarationsListe& declarations, statementsListe& statements) : NStatement("NBlock"), declarations(declarations), statements(statements) {}
    
  void afficher(){
    for(int i=0;i<declarations.size();++i){
      declarations[i]->afficher();
      cout<<endl;
    }
    for(int j=0;j<statements.size();++j){
      statements[j]->afficher();
      cout<<endl;
    }
  }
  void afficherMips(stringstream* ss){
    (*ss)<<"#begin"<<endl;
    (*ss)<<"L";
    afficherMipsL(ss);
    (*ss)<<":"<<endl;
    for(int i=0;i<statements.size();++i){
      statements[i]->afficherMips(ss);
    }
    (*ss)<<"#end"<<endl<<endl;
  }
};


// PROGRAMMES

class NProgramme : public Node {
public:
  NProgramme(string NomNoeud) : Node(NomNoeud){}
  virtual void afficher(){}
  virtual void afficherMips(stringstream* ss){}
};

class NArgument : public Node {
public:
  NArithmetique& variable;
  std::string type;
  NArgument(NArithmetique& variable, std::string type) : Node("NArgument"),variable(variable), type(type) {}


  void afficher(){
    variable.afficher();
    cout<<":"<<type;    
  }
  void afficherMips(stringstream* ss){}
};

class NProcedure : public NProgramme {
public:
  int LL;

  argumentsdeclarationListe& arguments;
  NStatement& statement;
  std::string nom;
  NProcedure(std::string nom,argumentsdeclarationListe& arguments, NStatement& statement) : NProgramme("NProcedure"), nom(nom),arguments(arguments), statement(statement) {
    if (STATIQUES::fonctions.find(nom) == STATIQUES::fonctions.end() ){
      LL = STATIQUES::L++;
      STATIQUES::fonctions[nom] = LL;
    }
    else
      LL = STATIQUES::fonctions[nom];
  }
    
  void afficher(){
    cout<<nom;
    for(int i=0;i<arguments.size();++i){
      arguments[i]->afficher();
      cout<<",";
    }

    cout<<endl;
    statement.afficher(); 
    cout<<endl;
  }
  void afficherMips(stringstream* ss){
    (*ss)<<"#"<<nom<<endl;
    (*ss)<<"L"<<LL<<":"<<endl;
    statement.afficherMips(ss);
    (*ss)<<endl;
  }
};

class NFonction : public NProgramme {
public:

  int LL;

  argumentsdeclarationListe& arguments;
  NStatement& statement;
  std::string nom;
  std::string type;
  NFonction(std::string nom,argumentsdeclarationListe& arguments, std::string type, NStatement& statement) : NProgramme("NFonction"), nom(nom),arguments(arguments), statement(statement), type(type) {
    if (STATIQUES::fonctions.find(nom) == STATIQUES::fonctions.end() ){
      LL = STATIQUES::L++;
      STATIQUES::fonctions[nom] = LL;
    }
    else
      LL = STATIQUES::fonctions[nom];
  }
    
  void afficher(){
    cout<<endl<<type<<" "<<nom;
    for(int i=0;i<arguments.size();++i){
      arguments[i]->afficher();
      cout<<",";
    }

    cout<<endl;
    statement.afficher(); 
    cout<<endl;
  }
  void afficherMips(stringstream* ss){
    (*ss)<<"#"<<nom<<endl;
    (*ss)<<"L"<<LL<<":"<<endl;
    statement.afficherMips(ss);
    (*ss)<<endl;
  }
}; 

class NProgrammePrincipal : public NProgramme {
public:
  programmedeclarationListe& fonctions_et_procedures;

  TableSymbole ts;
  NStatement& statement;
  NProgrammePrincipal(programmedeclarationListe& fonctions_et_procedures, NStatement& statement) : NProgramme("NProgrammePrincipal"), fonctions_et_procedures(fonctions_et_procedures), statement(statement) {
  }
    
  void afficher(){
    for(int i=0;i<fonctions_et_procedures.size();++i){
      fonctions_et_procedures[i]->afficher();
    }
    statement.afficher(); 
  }
  void afficherMips(stringstream* ss){
    (*ss)<<"main:"<<endl;
    (*ss)<<"j L";
    statement.afficherMipsL(ss);
    (*ss)<<endl<<endl; 
    for(int i=0;i<fonctions_et_procedures.size();++i){
      fonctions_et_procedures[i]->afficherMips(ss);
    }
    statement.afficherMips(ss);
  }

};

#endif
